/*
 * myftclient.c
 * CS 3251 Programming Assignment #2
 * Dustin Shiflett
 */ 

#include <stdio.h>      /* for printf() and fprintf() */
#include <sys/socket.h> /* for gethostbyname(), socket(), connect(), sendto(), and recvfrom() */
#include <netdb.h>      /* for gethostbyname() */
#include <arpa/inet.h>  /* for sockaddr_in and inet_addr() */
#include <stdlib.h>     /* for atoi() and exit() */
#include <string.h>     /* for memset() */
#include <unistd.h>     /* for close() and alarm() */
#include <errno.h>      /* for errno and EINTR */
#include <signal.h>     /* for sigaction() */
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/time.h>
#include "dropper.h"
#include "helperfuncs.h"

int debug, returnCode, sock;
struct ackPacket ACK;
struct initPacket initialReq;


int sendACK(int nextSeqNum) {
    ACK.progID = htonl(PROGID); /*could make it more efficient by not resetting this to the same value everytime, but not now--too much else to do*/
    ACK.nextSeqNumExpected = htonl(nextSeqNum);
    
    returnCode = sendto_dropper(sock, (void*) &ACK, sizeof(ackPacket), 0, (struct sockaddr*) &servAddr, servLen);
    
    if (returnCode < 0) {
        printf("Error sending ACK with sequence number %i.\n", nextSeqNum);
        fflsuh(stdout);
    }
}

int main(int argc, char *argv[]) {
    
    struct hostent *serverName;
    int serverPort, windowSize, maxPacketSize, lossPercent, temp1, temp2, sock, i, servLen;
    char* initialReqString, tempStr, bufLocation;
    char msgBuffer[MAX_WINDOW_SIZE];
    char uniqueID[10];
    char uIDCheck[10];
    struct sockaddr_in cliAddr, servAddr;
    
    
    debug = 0;
    /* check command line args: ./myftclient <server name> <port#> <remote filename> <local filename> <max packet size> <window size> <loss%> */
    if((argc < 8) || (argc > 9)) {
        printf("usage: %s <server name> <port#> <remote filename> <local filename> <max packet size> <window size> <loss percentage> [-d] \nServer Name: Domain Name or IP Address of Server\nServer Port: 1024-65535\nRemote Filename: exact name of the file to download from the remote server\nLocal Filename: name for the file on the local file system\nMax Packet Size: Maximum size to use for each packet (ignoring headers)\nMax Packet Size: 1024-32768 (in bytes)\nLoss Percentage: 0-100\n-d: Debug/Verbose Output\n", argv[0]);
    exit(1);
    }
    
    /*check for debug switch*/
    if(argc == 9) {
        if (strcmp("-d", argv[8]) == 0) {
            debug = 1;
            printf("Debug/verbose option enabled.\n");
        }
    }
    
    /*check for type constraints on parameters*/
    /*server name*/
    serverName = gethostbyname(argv[1]);
    if(serverName == NULL) {
        printf("%s: unknown host '%s' \n", argv[0], argv[1]);
        exit(1);
    }
    if (debug) printf("Desired server name acknowledged as %s, which translates to the IP address: %s.\n", argv[1], inet_ntoa(*(struct in_addr*)serverName->h_addr));
    
    /*server port*/
    serverPort = atoi(argv[2]);
    if(serverPort > 65535 || serverPort < 1024) {
        printf("%s: You attempted to use %i (or a non-integer) as the server port. Allowed ports are between 1024 and 65535. (Ports from 0 to 1023 are reserved for superusers only, and this program's server does not allow them to be used.)\nExiting.\n",argv[0], serverPort);
        exit(1);
    }
    if (debug) printf("Desired port acknowledged as %i.\n", serverPort);
    
    /*remote filename*/
    temp1 = strlen(argv[3]);
    if (temp1 > 255) {
        printf("%s: Remote Filename must be less than 255 characters in length. It appears that you tried to use a filename that was %i characters in length.\n", argv[0], temp1);
        exit(1);
    }
    
    if (debug) printf("Desired remote filename acknowledged as %s.\n", argv[3]);
    
    /*local filename*/
    temp1 = strlen(argv[4]);
    if (temp1 > 255) {
        printf("%s: Local Filename must be less than 255 characters in length. It appears that you tried to use a filename that was %i characters in length.\n", argv[0], temp1);
        exit(1);
    }
    
    if (debug) printf("Desired local filename acknowledged as %s.\n", argv[4]);
    
    /*max packet size*/
    maxPacketSize = atoi(argv[5]);
    if(maxPacketSize > MAX_WINDOW_SIZE || maxPacketSize < 1) {
        temp2 = MAX_WINDOW_SIZE;
        printf("%s: You attempted to use %i (or a non-integer) as the max packet size. The minimum packet size allowed is 1 and the maximum allowed is %i.\nExiting.\n", argv[0], maxPacketSize, temp2);
        exit(1);
    }
    if (debug) printf("Desired max packet size acknowledged as %i.\n", maxPacketSize);
    
    /*window size*/
    windowSize = atoi(argv[6]);
    if(windowSize > MAX_WINDOW_SIZE || windowSize < MIN_WINDOW_SIZE) {
        temp1 = MIN_WINDOW_SIZE;
        temp2 = MAX_WINDOW_SIZE;
        printf("%s: You attempted to use %i (or a non-integer) as the window (buffer) size. The minimum window size allowed is %i and the maximum allowed is %i.\nExiting.\n", argv[0], windowSize, temp1, temp2);
        exit(1);
    }
    if (debug) printf("Desired max window size acknowledged as %i.\n", windowSize);
    /*Ensure that at least one packet's data can fit in our window (buffer)*/
    if(windowSize < maxPacketSize) {
        printf("%s: You attempted to use %i as the window (buffer) size, and %i as the max packet size. The max packet size will automatically be adjusted to be equal to the window size.\n", argv[0], windowSize, maxPacketSize);
        maxPacketSize = windowSize;
        if (debug) printf("The max packet size has been adjusted to %i.\n", maxPacketSize);
    }
    
    /*loss percent*/
    lossPercent = atoi(argv[7]);
    if(lossPercent < 0 || lossPercent > 100) {
        printf("%s: You attempted to use %i (or a non-integer) as the loss percentage. The loss percentage parameter should be a value between 0 and 100.\nExiting.\n", argv[0], lossPercent);
        exit(1);
    }
    if (debug) printf("Desired loss percentage acknowledged as %i.\n", lossPercent);
    
    returnCode = set_dropper(lossPercent);
    if (debug) printf("Dropper set to %i percent loss.\n", lossPercent);
    
    
    if (debug) printf("All command line parameters accepted.\n");
    /*Done checking input from commandline*/
    
    /*Construct our Initial Request Packet Payload*/
    initialReq.progID = htonl(PROGID);
    initialReq.seqNumber = htonl(0);
    initialReq.maxPacketLength = htonl(maxPacketSize);
    initialReq.windowLength = htonl(windowSize);
    /*unsure about converting char to network byte order at the moment, cross our fingers*/
    strncpy(initialReq.fileName, argv[3], 256);
    
    if (debug) printf("Initial request packet is now constructed.\n");
    
    /*Time to create our socket*/
    /*possible problem: h_addr_list[0] here and h_addr for debug output*/
    servAddr.sin_family = serverName->h_addrtype;
    memcpy((char *) &servAddr.sin_addr.s_addr, serverName->h_addr_list[0], serverName->h_length);
    servAddr.sin_port = serverPort;
    
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock < 0) {
        printf("%s: cannot open socket\n", argv[0]);
        fflush(stdout);
        exit(1);
    }
    if (debug) printf("Socket created and open.\n");
    
    /*bind to any port*/
    cliAddr.sin_family = AF_INET;
    cliAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    cliAddr.sin_port = htons(0);
    
    returnCode = bind(sock, (struct sockaddr *) &cliAddr, sizeof(cliAddr));
    if(returnCode < 0) {
        printf("%s: cannot bind port\n", argv[0]);
        fflush(stdout);
        closeSocket(sock);
        exit(1);
    }
    if (debug) printf("Port bound.\n");
    
    /*initialize our message buffer before we do any communication with server*/
    memset(msgBuffer, 0x0, MAX_BUFFER_SIZE);
    
    
    /*send initial request data to server*/
    if (debug) printf("Sending max packet size, window size, and the requested file name to server.\n");
    
    returnCode = 0;
    temp1 = 0;
    i = 1;
    while (temp1 == 0) {
        
        returnCode = sendto_dropper(sock, &initialReq, sizeof(initialReq), 0, (struct sockaddr *) &servAddr, sizeof(servAddr));
        if(returnCode != sizeof(initialReq)) {
            printf("%s: did not send initial request data. The return code was %i. Trying again for the %i time.\n", argv[0], returnCode, i);
            i++;
        } else if (returnCode == sizeof(initialReq)) {
            temp1 = 1;
            if (debug) printf("Initial request packet sent to server on try %i.\n", i);
        }
        
        if(i > MAX_RETRY) {
            printf("Something is wrong. The maximum retry count has been met at %i. The client is giving up. Exiting.\n", i);
            closeSocket(sock);
            fflush(stdout);
            exit(1);
        }
    }

    /*listen for server response--should I expect a file or not?*/
    
    /*BELOW HERE IS OUTDATED AND INCORRECT*/
    if (debug) printf("Waiting for response from server...\n");
    
    returnCode = 0;
    i = 0;
    
    while(i < 12) {
        /*receive message from server*/
        i += returnCode;
        servLen = sizeof(servAddr);
        returnCode = recvfrom(sock, msgBuffer + i, MAX_WINDOW_SIZE, 0, (struct sockaddr *) &servAddr, &servLen);
        
        if(returnCode < 0) {
            printf("%s: cannot receive data. Error code: %i.\n", argv[0], returnCode);
        }
        
        i += returnCode;
    }
    
    bufLocation = memccpy(uIDCheck, memBuffer, '\0', 10)
    if (bufLocation == NULL) {
        printf("%s: Something went wrong. The packet we received did not contain any NULL characters, so it isn't from our server.\n", argv[0]);
        exit(1);
    }
    
    if(strcmp(uIDCheck, uniqueID) != 0) {
        printf("%s: unique IDs do not match on the packet we received.\n", argv[0]);
        exit(1);
    }
    
    if(bufLocation == '1') {
        printf("%s: server accepted our request. Begin file transfer!\n", argv[0]);
    }
    if(bufLocation == '0') {
        printf("%s: server denied our request. Perhaps the file does not exist? Exiting.\n", argv[0]);
        exit(1);
    }
    
    if (debug) printf("We are going to start listening for our file, now.\n ");
    
    return(1);
}
